<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>1.vue数据驱动视图原理</title>
</head>

<body>
  <!-- Vue数据双向绑定的原理 -->
  通过Object.defineProperty()中get() set() 监听数据的变换

  缺点：1.深度监听，需要递归到底，一次性计算量大
        2.无法监听新增数据和删除数据(Vue.set Vue.delete 接口监听)



  <script>
    function updateView() {
      console.log('视图更新');
    }

    // 重新定义数组原型
    const oldArrayProperty = Array.prototype
    // 创建新对象，原型指向oldArrayProperty, 在拓展的方法不会影响原型
    const arrProto = Object.create(oldArrayProperty);
    ['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {
      arrProto[methodName] = function() {
        updateView() // 监听视图更新
        oldArrayProperty[methodName].call(this, ...arguments)
      }
    })

    function defineReactive(target, key, value) { // data info {...}
      observer(value)

      // 核心API
      Object.defineProperty(target, key, {
        get() {
          return value
        },
        set(newValue) {
          if (newValue !== value) {
            // 设置新值
            // 注意 value一直在闭包中，此处设置完之后，get也是value
            value = newValue

            // 触发更新视图
            updateView()
          }
        }
      })
    }


    function observer(target) {
      if (typeof target !== 'object' || target === null) {
        // 不是对象或者数组
        return target
      }
      // cars
      if (target instanceof Array) {
        target.__proto__ = arrProto
      }

      // 重新定义各个属性
      for (let key in target) {
        defineReactive(target, key, target[key])
      }
    }



    const data = {
      name: 'zhangsan',
      age: 20,
      info: {
        address: '北京'
      },
      cars: [1,2,3]
    }

    observer(data)

    // 测试
    // data.name = 'lisi'
    // data.age = 23
    // data.info.address = '南京'
    data.cars.push(4)
  </script>

</body>

</html>